//! config file parser
//!
//! Default config file is [".server.json"]
//!
//! Json-Server will take the data from the config file
//!
//! You can DIY your custom config data in this module
//!

use path_disp::PathDisp;

use crate::{error, utils::JsonValueTo, ForceUnwrap, DEFAULT_FILE};

use std::io::prelude::*;
use std::{fs::File, path::Path};

/// Type schema for config.services
#[derive(Clone, Debug, Default)]
pub struct Service {
    pub url: String,
    pub name: String,
    pub desp: String,
    pub file: String,
}

impl Service {
    /// set file name to default value
    pub fn from_file<P: AsRef<Path>>(path: P) -> Self {
        let path = path.as_ref();
        let file = path.to_str().unwrap().to_string();
        let name = path.file_stem().unwrap().to_str().unwrap().to_string();
        let url = format!("/{}", name);
        Self {
            url,
            name,
            file,
            desp: "no description".to_string(),
        }
    }
}

/// Type schema for config
#[derive(Clone, Debug)]
pub struct Config {
    pub name: String,
    pub port: String,
    pub title: String,
    pub services: Vec<Service>,

    pub file: String,
    pub dir: String,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            name: "JsonServer".to_string(),
            port: "4000".to_string(),
            title: "JsonServer".to_string(),
            services: vec![],
            file: "".to_string(),
            dir: "".to_string(),
        }
    }
}

impl From<json::JsonValue> for Config {
    fn from(v: json::JsonValue) -> Self {
        let name = v["name"].null_or_string(Self::default().name);
        let port = v["port"].null_or_string(Self::default().port);
        let title = v["title"].null_or_string(Self::default().title);
        let mut services = Vec::new();

        for i in 0..v["services"].len() {
            let url = v["services"][i]["url"].to_string();
            let name = v["services"][i]["name"].to_string();
            let desp = v["services"][i]["desp"].to_string();
            let file = v["services"][i]["file"].to_string();
            services.push(Service {
                url,
                name,
                desp,
                file,
            });
        }

        Self {
            name,
            port,
            title,
            services,
            ..Self::default()
        }
    }
}

impl Config {
    /// read file to get Config struct
    pub fn from_file<P: AsRef<Path>>(path: P) -> Self {
        let mut config = Config::from(from_file(&path).unwrap());
        let dir = path
            .as_ref()
            .parent()
            .unwrap()
            .to_str()
            .unwrap()
            .to_string();
        let file = path.as_ref().to_str().unwrap().to_string();
        config.dir = dir;
        config.file = file;
        config
    }

    /// get the config file path from env args
    pub fn get_file() -> String {
        let mut file = std::env::args()
            .nth(1)
            .map_or(DEFAULT_FILE.to_string(), |s| s);

        if Path::new(&file).is_dir() {
            file = Path::new(&file).resolve(DEFAULT_FILE);
        }

        if Path::new(&file).is_absolute() {
            return file;
        }

        Path::new(&std::env::current_dir().force_unwrap()).resolve(&file)
    }

    /// get config data from env args
    pub fn from_env() -> Self {
        let path = Self::get_file();

        if !Path::new(&path).exists() {
            println!("config file [\"{}\"] is not exist", path);
            std::process::exit(0);
        }

        Config::from_file(path)
    }
}

/// Get data for config.file
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<json::JsonValue, error::ConfigError> {
    let mut f = File::open(path)?;
    let mut buf = String::new();
    f.read_to_string(&mut buf)?;
    Ok(json::parse(&buf)?)
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_read_file() {
        let config = Config::from_file("examples/server.json");
        println!("{:#?}", config);
    }
}
